#pragma once

#include <string>
#include <unordered_map>
#include <memory>

#include "rocksdb/slice.h"

#include "Status.h"



namespace streamdb {

class StreamInfo;

struct Block {
    std::string key;
    size_t maxBlockSize;
    char * data;
    std::shared_ptr<StreamInfo> meta;
    size_t used;
    bool isDirty;

    Block(const std::string & key, size_t maxBlockSize, std::shared_ptr<StreamInfo> & meta)
    : key(key)
    , maxBlockSize(maxBlockSize)
    , data(new char[maxBlockSize])
    , meta(meta)
    , used(0)
    , isDirty(false)
    {
    }
    ~Block(){
        delete [] data;
    }
    
    Block(Block && other){
        *this = std::move(other);
    }
    Block & operator=(Block && other){
        if(this != &other){
            key = std::move(other.key);

            used = std::move(other.used);
            other.used = 0;

            maxBlockSize = std::move(other.maxBlockSize);
            other.maxBlockSize = 0;

            delete [] data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
    Block(const Block & other) = delete;
    Block & operator=(const Block & other) = delete;

    size_t getFreeBytes() const{
        return maxBlockSize - used;
    }

    int append(const char * pos, const size_t size){
        //TODO: argumentCheck 
        size_t free = getFreeBytes();
        if(free > 0 && size > 0) {
            size_t writeBytes = (free >= size) ? size : free;
            memcpy(data + used, pos, writeBytes);
            used += writeBytes;
            isDirty = true;
            return writeBytes;
        }
        isDirty = false;
        return free;
    }
};

//TODO: Single Test 
class Cache {
public:
    Cache(){}
    virtual ~Cache(){}
    Block * getBlock(const rocksdb::Slice & key){
        auto iter = blockMap.find(key.data());
        if(iter != blockMap.end()){
            return iter->second;
        }
        return nullptr;
    }
    Status insert(const rocksdb::Slice & key, Block * value){
        auto iter = blockMap.find(key.data());
        if(iter != blockMap.end()){
            return Status::ExistAlready();
        }
        blockMap.insert(std::make_pair(std::string(key.data()), value));
        return Status::OK();
    }
private:
    std::unordered_map<std::string, Block *> blockMap;
};


class BlockManager {
public:
    BlockManager();
    ~BlockManager();

    Status regStream(const std::string & streamName);
    Status unregStream(const std::string & streamName);

    size_t getStreamCount() const { return streamMap.size();}

    size_t getStreamBlockCount(const std::string & stream) ;

    Block * newBlock(const std::string & streamName);

    Status append(const std::string & streamName, const rocksdb::Slice & msg);
    size_t getMaxBlockSize() const ;

    Status flushStream(const std::string & stream);//foreach ???
    Status flush() ;

private:

    std::shared_ptr<StreamInfo> getStream(const std::string & streamName);
    Block *  getLastBlockFromStream(const std::string streamName);

    std::string getBlockId(const std::string & streamName, uint64_t offset) const;
    std::string getAbsPath(const std::string & streamName);

private:
    size_t maxBlockSize;
    std::unordered_map<std::string, std::shared_ptr<StreamInfo>> streamMap;
    Cache * cache;
};

}//namespace streamdb
